//  a simple implementation for a pipelined cpu with a MIPS instruction set

//import the sub modules
`include "alu.v"
`include "reg_file.v"
`include "data_mem.v"
`include "instr_mem.v"
`include "clock.v"


module PipeController (reset, clk);
  input reset; //global reset signal, 1 bit
  input clk; //global clock signal, flip-flop


  parameter mem_addr_size=32;
  parameter instr_size=32;

  parameter reg_addr_size=5;
  parameter op_size=6;
  parameter func_size=6;
  parameter aluc_size=6;
  parameter j_addr_size=26;
  parameter imm_size=16;
  parameter phase_num=5;

  parameter word_size=32;
  parameter FALSE=1'b0;
  parameter TRUE=1'b1;
  parameter NOP=32'b0;




  // System global states
  reg [mem_addr_size-1:0] pc; //PC pointer
  reg stall=FALSE; //a global stall signal for a bug-like data hazard



  // IF states
  reg [phase_num-1:0] enabler=5'b11111; //for convenience, let the phase enabler be an array [0:4]= {PC->PC', IF->ID, ID->EX, EX->MEM, MEM->WE}


  //IF/ID registers
  reg [instr_size-1:0]  id_instr=32'b0; //init as nop
  reg [j_addr_size-1:0] id_j_addr=26'b0;



  reg [op_size-1:0] id_op=6'b0;
  reg [reg_addr_size-1:0] id_rs=5'b0, id_rt=5'b0, id_rd=5'b0, id_sa=5'b0;
  reg [func_size-1:0] id_func=6'b0;




  wire [instr_size-1:0] w_if_instr;




  //ID/EX registers
  wire [op_size-1:0] w_id_op;
  wire [j_addr_size-1:0] w_id_j_addr;
  wire [imm_size-1:0] w_id_imm;
  wire [reg_addr_size-1:0] w_id_rs, w_id_rt, w_id_rd, w_id_sa;
  wire [func_size-1:0] w_id_func;


  //Other among-phase registers





  //IF phase flags
  reg id_aluimm=FALSE, id_wreg=FALSE, id_wmem=FALSE, id_jump=FALSE, id_m2reg=FALSE, id_shift=FALSE, id_rmem=FALSE;
  reg [aluc_size-1:0] id_aluc=6'b0;
  reg [word_size-1:0] id_signed_imm=32'b0;
  reg [reg_addr_size-1:0] id_reg_addr=5'b0;


  wire w_id_aluimm, w_id_regrt, w_id_wreg, w_id_wmem, w_id_jump, w_id_m2reg, w_id_shift, w_id_sext;
  wire w_id_rmem;
  wire [aluc_size-1:0] w_id_aluc;
  wire [word_size-1:0] w_id_signed_imm;
  wire [reg_addr_size-1:0] w_id_reg_addr;



  //EXE phase flags
  reg [reg_addr_size-1:0] ex_sa=5'b0;
  reg [word_size-1:0]   ex_in_a_val=32'b0, ex_in_b_val=32'b0, ex_signed_imm=32'b0, ex_out_val=32'b0, ex_b_val=32'b0;
  reg [reg_addr_size-1:0] ex_reg_rs=5'b0, ex_reg_rt=5'b0;
  reg [reg_addr_size-1:0] ex_reg_addr=5'b0;


  wire [reg_addr_size-1:0] w_ex_rs, w_ex_rt;
  wire [word_size-1:0] w_out_a_val, w_out_b_val, w_ex_a_val, w_ex_b_val, w_ex_out_val;
  wire w_ex_z_flag;


  //flags
  reg [aluc_size-1:0] ex_aluc=6'b0; // which is passed from the former stage
  reg ex_aluimm=FALSE, ex_wreg=FALSE, ex_wmem=FALSE, ex_m2reg=FALSE, ex_shift=FALSE, ex_rmem=FALSE; //this is enough





  //MEM phase flags
  reg [word_size-1:0] mem_in_val=32'b0;
  reg [mem_addr_size-1:0] mem_addr=32'b0;
  reg [reg_addr_size-1:0] mem_reg_addr=5'b0;
  wire [word_size-1:0] w_mem_out_val;
  reg mem_wreg=FALSE, mem_wmem=FALSE, mem_m2reg=FALSE, mem_rmem=FALSE; //this is enough





  //WE phase flags

  reg [reg_addr_size-1:0] we_reg_addr=5'b0;
  reg [word_size-1:0] we_in_val, we_possible_val;
  reg we_wreg=FALSE, we_m2reg=FALSE; //this is enough


  //flags for hazard

  wire [4:0] w_enabler;

  reg eb=TRUE;
  reg ex_flush=FALSE;

  wire inverse_clk= ~clk;




  //submodules used
  IFetcher fetcher(pc, reset, w_if_instr);
  IDecoder decoder(clk, id_instr, w_id_op, w_id_rs, w_id_rt, w_id_rd, w_id_sa, w_id_func, w_id_imm, w_id_j_addr);

  ALU executor(w_ex_a_val, w_ex_b_val, ex_aluc, w_ex_out_val, w_ex_z_flag);
  DataMem mem(mem_addr, mem_in_val, mem_wmem, reset, w_mem_out_val);


  ControlUnit control_unit(w_id_func, w_id_op, w_id_aluimm, w_id_regrt, w_id_wreg, w_id_wmem, w_id_jump, w_id_m2reg, w_id_shift, w_id_sext, w_id_aluc, w_id_rmem); //compositive logic, decorator
  //module RegFile (addr_a, addr_b, addr_w, in_val, we, reset, out_a, out_b, clk); posedge read, negedge write


  //use wired connection to the register file, the data will be prepared at the next pos edge
   RegFile reg_file(id_rs, id_rt, we_reg_addr, we_in_val , we_wreg, reset, w_out_a_val, w_out_b_val, clk); //will be prepared until posedge

   SignExtender sign_extender(w_id_imm,w_id_signed_imm, w_id_sext); //compositive logic, decorator
   wire [word_size-1:0] w_ex_reg_b_val;


   A_Selector a_selector(ex_shift, ex_reg_rs , ex_in_a_val, ex_sa, w_ex_a_val, mem_wreg, we_wreg, mem_reg_addr, we_reg_addr, mem_addr, we_in_val);
   B_Selector b_selector(ex_aluimm, ex_reg_rt, ex_in_b_val, ex_signed_imm, w_ex_b_val, w_ex_reg_b_val ,mem_wreg, we_wreg, mem_reg_addr, we_reg_addr, mem_addr, we_in_val);


   RegSelector reg_selector(w_id_rt, w_id_rd, w_id_regrt ,w_id_reg_addr);


   always @ (reset) begin
     if(reset==1'b1) begin
      pc<=32'b0;
     end
   end


  always @ (posedge clk) begin
    //executor implicitly computes here, since register value will not be prepared until pos edge
    //transfer data from EX to MEM, one cycle

  end


  //prepare the address
  always @ (negedge clk) begin
    //which is the third kind data hazard
    if(ex_rmem==TRUE && ((ex_reg_rt==id_rs) || (ex_reg_rt==id_rt))) begin
      stall<=TRUE; //the next turn, stall the first two stage
    end



    //transfer data from IF to ID
      if(stall==FALSE) begin

        id_instr<= (w_id_jump==TRUE)?  NOP:w_if_instr; //flush the instruction that is already in the IF phase register
        id_j_addr<=w_id_j_addr;
        id_op<=w_id_op;
        id_rs<=w_id_rs;
        id_rt<=w_id_rt;
        id_sa<=w_id_sa;
        id_rd<=w_id_rd;
        id_func<=w_id_func;

        id_aluimm<=w_id_aluimm;
        id_wreg<=w_id_wreg;
        id_wmem<=w_id_wmem;
        id_rmem<=w_id_rmem;
        id_jump<=w_id_jump;
        id_m2reg<=w_id_m2reg;
        id_shift<=w_id_shift;

        id_aluc<=w_id_aluc;
        id_signed_imm<=w_id_signed_imm;
        id_reg_addr<=w_id_reg_addr;


    //transfer data from ID to EX(both flags part and the register file part), one cycle
        //reg part
        ex_reg_rs<=id_rs;
        ex_reg_rt<=id_rt;


        //data part
        ex_aluc<=id_aluc;
        ex_signed_imm<=id_signed_imm;
        ex_sa<=id_sa;

        //flag part
        ex_aluimm<=id_aluimm;
        ex_shift<=id_shift;

        ex_wmem<=id_wmem;
        ex_m2reg<=id_m2reg;


        ex_reg_addr<=id_reg_addr;
        ex_wreg<=id_wreg;

        ex_out_val<=w_ex_out_val;
        ex_b_val<=w_out_b_val;
        ex_in_a_val<=w_out_a_val; //set to the value comes from the register file recently
        ex_in_b_val<=w_out_b_val; //the same
        ex_rmem<=id_rmem;
        //update pc
        pc<=(w_id_jump==TRUE)? {2'b00,{pc[word_size-1:word_size-4]},w_id_j_addr}:pc+1;

      end
      else begin
        stall=FALSE; //reset the stall signal
      end


        mem_wreg<=ex_wreg;
        mem_m2reg<=ex_m2reg;

        mem_reg_addr<=ex_reg_addr;
        //prepare data and address for MEM
        //do data forward

        mem_addr<=w_ex_out_val;
        mem_in_val<=w_ex_reg_b_val;
        mem_wmem<=ex_wmem;
        mem_rmem<=ex_rmem;



        //transfer data from MEM to WE
        we_wreg<=mem_wreg;
        we_m2reg<=mem_m2reg;
        we_reg_addr<=mem_reg_addr;

        we_in_val<=(mem_m2reg)? w_mem_out_val: mem_addr;




  end


  //the first five phase is called the pipeline setting phase, which should be manually controlled;
endmodule





module IFetcher(pc, reset,  IF_ID_instr);
  parameter mem_addr_size=32;
  parameter instr_size=32;
  parameter JUMP=6'b000010;
  parameter NOP=32'b0;

  input [mem_addr_size-1:0] pc;
  input reset; //a signal only be used when the system starts

  output [instr_size-1:0] IF_ID_instr;
  reg IF_ID_instr;
  wire [instr_size-1:0] w_instr; //for module fetch




  //module InstrMem (addr_a, out_a, reset);
  InstrMem instr_mem(pc,w_instr,reset);


  //implement it as an async logic, fetch the data when pc changed
  always @ (pc or w_instr) begin
        IF_ID_instr<=w_instr;
  end
endmodule


// the module that implements the ID phase, this decoder only serve with basic usage
module IDecoder(clk, instr, op, rs, rt, rd, sa, func, imm, j_addr);
  parameter reg_addr_size=5;
  parameter instr_size=32;
  parameter op_size=6;
  parameter func_size=6;
  parameter j_addr_size=26;
  parameter imm_size=16;

  parameter rs_hi=instr_size-op_size;
  parameter rt_hi=rs_hi-reg_addr_size;
  parameter rd_hi=rt_hi-reg_addr_size;
  parameter sa_hi=rd_hi-reg_addr_size;
  parameter func_hi=sa_hi-reg_addr_size;


  input clk;
  input [instr_size-1:0] instr;
  input enable;
  output [op_size-1:0] op;
  output [j_addr_size-1:0] j_addr;
  output [imm_size-1:0] imm;
  output [reg_addr_size-1:0] rs,rd,rt,sa;
  output [func_size-1:0] func;

  reg op,rs,rd,rt,sa,func,imm,j_addr;


  always @ (instr) begin
      //do demux without consider the overlapping
        op<=instr[instr_size-1:instr_size-op_size];
        rs<=instr[rs_hi-1:rt_hi];
        rt<=instr[rt_hi-1:rd_hi];
        rd<=instr[rd_hi-1:sa_hi];
        sa<=instr[sa_hi-1:func_hi];
        func<=instr[func_hi-1:0];
        imm<=instr[imm_size-1:0];
        j_addr<=instr[j_addr_size-1:0];
  end
endmodule


module A_Selector( shift, rs, a_val, sa, out, mem_wreg, we_wreg, mem_reg_addr, we_reg_addr, mem_addr, we_in_val);
  parameter word_size=32;
  parameter reg_addr_size=5;
  parameter TRUE=1'b1;
  parameter FALSE=1'b0;

  input shift;
  input clk;
  input [word_size-1:0] a_val;


  input [word_size-1:0] mem_addr, we_in_val;
  input [reg_addr_size-1:0] mem_reg_addr, we_reg_addr;
  input mem_wreg, we_wreg;

  input [reg_addr_size-1:0] sa,rs;

  output [word_size-1:0] out;


  reg out;
//
  always @ (shift or a_val or sa or rs or mem_wreg or we_wreg or mem_reg_addr or we_reg_addr or mem_addr or we_in_val) begin
    if(shift) begin
      out<=sa;
    end
    else begin
      if(mem_wreg==TRUE && mem_reg_addr!=5'b0 && mem_reg_addr==rs) begin
          out<=mem_addr; //forward from some instr that in the WB phase
      end
      else if(we_wreg==TRUE && we_reg_addr!=5'b0 && mem_reg_addr!=rs && we_reg_addr==rs) begin
          out<=we_in_val; //forward from some instr that in the Mem phase
      end
      else begin
        out<=a_val; //the normal value
      end
    end
  end
endmodule

module B_Selector(aluimm, rt,  b_val, signed_imm, out, out_b, mem_wreg, we_wreg, mem_reg_addr, we_reg_addr, mem_addr, we_in_val);
  parameter word_size=32;
  parameter reg_addr_size=5;
  parameter TRUE=1'b1;
  parameter FALSE=1'b0;
  input clk;
  input aluimm;
  input [word_size-1:0] b_val;
  input [word_size-1:0] signed_imm;

  input [word_size-1:0] mem_addr, we_in_val;

  input [reg_addr_size-1:0] mem_reg_addr, we_reg_addr;
  input mem_wreg, we_wreg;


  input [reg_addr_size-1:0] rt;
  output [word_size-1:0] out;
    output [word_size-1:0] out_b;



  reg out, out_b;

  always @ (aluimm or rt or b_val or signed_imm or mem_wreg or we_wreg or mem_reg_addr or we_reg_addr or mem_addr or we_in_val) begin
    if(aluimm) begin
      out<=signed_imm;
      out_b<=b_val;
    end else begin
    if(mem_wreg==TRUE && mem_reg_addr!=5'b0 && mem_reg_addr==rt) begin
        out<=mem_addr; //forward from some instr that in the WB phase
    end
    else if(we_wreg==TRUE && we_reg_addr!=5'b0 && mem_reg_addr!=rt && we_reg_addr==rt) begin
        out<=we_in_val; //forward from some instr that in the Mem phase
    end
    else begin
        out<=b_val;
    end
   end

   if(mem_wreg==TRUE && mem_reg_addr!=5'b0 && mem_reg_addr==rt) begin
       out_b<=mem_addr;
   end
   else if(we_wreg==TRUE && we_reg_addr!=5'b0 && mem_reg_addr!=rt && we_reg_addr==rt) begin
       out_b<=we_in_val;
   end
   else begin
       out_b<=b_val;
   end
  end
endmodule



//which wraps the ALU with control logic
module IExecutor(clk, a_val, b_val, aluc, out_val, z_flag);
  parameter word_size=32;
  parameter aluc_size=6;
  input [word_size-1:0] a_val, b_val;
  input [aluc_size-1:0] aluc;
  input clk;
  output [word_size-1:0] out_val;
  output z_flag;

  reg out_val;
  reg z_flag;

  wire [word_size-1:0] w_out_val;
  wire w_z_flag;
  ALU alu(a_val,b_val, aluc, w_out_val,w_z_flag);

  always @ (negedge clk) begin
      out_val<=w_out_val;
      z_flag<=w_z_flag;
  end

endmodule


module ControlUnit(func, op, aluimm, regrt, wreg, wmem, jump, m2reg, shift, sext, aluc, rmem);
  parameter op_size=6;
  parameter func_size=6;
  parameter aluc_size=6;
  //defined instructions
  parameter JUMP=6'b000010;
  parameter SPECIAL=6'b0; // NOP is also included in it
  parameter ADDI=6'b001000;
  parameter ANDI=6'b001100;
  parameter ORI=6'b001101;
  parameter SLTI=6'b001010;
  parameter SW=6'b101011;
  parameter LW=6'b100011;

  parameter SHIFT=6'b0; //the func code for shift

  parameter TRUE=1'b1;
  parameter FALSE=1'b0;

  parameter ADD=6'b100000;
  parameter AND=6'b100100;
  parameter OR=6'b100101;
  parameter SLT=6'b101010;
  parameter SUB=6'b100010;
  //mostly for nop, for a general implementation of instructions
  parameter SLL=6'b000000;



  input clk;
  input [func_size-1:0] func;
  input [op_size-1:0] op;

  output aluimm, regrt, wreg, wmem, jump, m2reg, shift, sext, rmem;
  output [aluc_size-1:0] aluc;


  reg aluimm, regrt, wreg, wmem, jump, m2reg, shift, sext, aluc, rmem;

  always @ (op or func) begin
    case (op) SPECIAL: begin //for ADD, AND, OR, SUB, SLT, NOP
            aluimm<=FALSE;
            regrt<=FALSE;
            wreg<=TRUE; //only a mark
            wmem<=FALSE;
            rmem<=FALSE;
            jump<=FALSE;
            m2reg<=FALSE; //nothing to do with memory
            //branch<=FALSE;
            shift<=(func==SHIFT)? TRUE:FALSE;
            sext<=FALSE;
            aluc<=func;
        end
        ADDI,ANDI,ORI,SLTI: begin
            aluimm<=TRUE;
            regrt<=TRUE; //use rt as the dst register
            wreg<=TRUE; //only a mark
            wmem<=FALSE;
            rmem<=FALSE;
            jump<=FALSE;
            m2reg<=FALSE;
            shift<=FALSE;
            sext<=TRUE;
            case (op)
              ADDI: aluc<=ADD;
              ANDI: aluc<=AND;
              ORI: aluc<=OR;
              SLTI: aluc<=SLT;
            endcase
        end
        JUMP:begin
            //absolute address, nothing to do with the alu
            aluimm<=FALSE;
            regrt<=FALSE;
            wreg<=FALSE;
            wmem<=FALSE;
            rmem<=FALSE;
            jump<=TRUE;
            m2reg<=FALSE;
            //branch<=TRUE;
            shift<=FALSE;
            sext<=FALSE;
            aluc<=ADD;
        end
        SW,LW:begin
          aluimm<=TRUE; //for offset
          regrt<=TRUE;
          wreg<=(op==LW)? TRUE:FALSE; //if load word, register modified. only a mark
          wmem<=(op==SW)? TRUE:FALSE; //if store word, memory modified. only a mark
          rmem<=(op==LW)? TRUE:FALSE;
          jump<=FALSE;
          m2reg<=(op==SW)? FALSE:TRUE;
          //branch<=FALSE;
          shift<=FALSE;
          sext<=TRUE; //signed extend offset
          aluc<=ADD;
        end
        default: $display("OP %x not Defined",op);
      endcase
  end

endmodule


module SignExtender(imm, signed_imm, sext);
  parameter imm_size=16;
  parameter word_size=32;

  input [imm_size-1:0] imm;
  input sext;
  output [word_size-1:0] signed_imm;
  reg signed_imm;
  reg sign;

  always @ (imm or sext) begin
    sign=(sext)? imm[imm_size-1]:1'b0;
    signed_imm={{(word_size-imm_size){sign}},imm};
  end
endmodule

module RegSelector(rt, rd, regrt, out);
  parameter reg_addr_size=5;

  input [reg_addr_size-1:0] rt, rd;
  input regrt;

  output [reg_addr_size-1:0] out;
  reg out;


  always @ (regrt or rt or rd) begin
      out<=(regrt)? rt:rd;
  end
endmodule

module pipe_cpu_sim ();

  wire clk;
  reg reset;

  ClockGen clock_generator(clk);
  PipeController pipe_cpu(reset,clk);

  initial begin
    $dumpfile("pipe_cpu_sim.vcd");
    $dumpvars(0,pipe_cpu_sim);
    reset=0;
    //init the state before #10 the first pos edge
    #1 reset=1;
    #1 reset=0;

    #300 $finish;
  end


endmodule // simulation for single cycle cpu

//a simple module to deal with data hazard
module HazardDealer(clk, eb ,a_reg, b_reg, we_reg, we_addr, enabler);
  parameter reg_addr_size=5;

  input eb;
  input clk;
  input [reg_addr_size-1:0] a_reg, b_reg, we_addr;
  input we_reg;
  output [4:0] enabler;
  reg enabler;


  always @ (negedge clk or eb) begin
    if(eb) begin
      if(we_reg) begin
        if((a_reg==we_addr && we_addr!=5'b0) || (b_reg==we_addr && we_addr!=5'b0)) begin
          enabler<=5'b01100;
        end
      end
      else begin
          enabler<=5'b11111;
      end
    end
  end
endmodule
